from tokenizer import TokenType
from exception import TypeJsonParseError
import ast
import decimal
import consts
import sys


class Parser:
    def __init__(self, tokenizer):
        self.tk = tokenizer
        self.ast = {
            consts.AST_MODULE: None,
            consts.AST_USE: [],
            consts.AST_DEF: [],
            consts.AST_VALUE: None
        }
        self.expecting_module = True
        self.expecting_use = True
        self.expecting_def = True
        self.expecting_value = True
        self.current = None

        self._maxunicode = sys.maxunicode

    def __next_token(self):
        self.current = self.tk.next()

    def parse(self):
        while True:
            self.__next_token()
            token = self.current
            tk_type = token.type
            value = token.value

            if tk_type == TokenType.END_DOC:
                # parse finish
                return self.ast
            elif tk_type == TokenType.KEY:
                if value == 'def':
                    self.expecting_module = False
                    self.expecting_use = False
                    if self.expecting_def:
                        self.ast[consts.AST_DEF].append(self.parse_def())
                    else:
                        raise TypeJsonParseError('parser', '`def` should appear before `object`', token.line_col)
                elif value == 'use':
                    self.expecting_module = False
                    if self.expecting_use:
                        self.ast[consts.AST_USE].append(self.parse_use())
                    else:
                        raise TypeJsonParseError('parser', '`use` should appear before `def` and `object`',
                                                 token.line_col)
                elif value == 'module':
                    if self.expecting_module:
                        self.expecting_module = False
                        self.ast[consts.AST_MODULE] = self.parse_module()
                    else:
                        raise TypeJsonParseError('parser', '`module` should be the first statement of the file',
                                                 token.line_col)
                else:
                    raise Exception('bug: unknown keyword: ' + value)
            else:
                if self.expecting_value:
                    self.expecting_module = False
                    self.expecting_use = False
                    self.expecting_def = False
                    self.expecting_value = False
                    o = self.parse_value()
                    self.ast[consts.AST_VALUE] = o
                else:
                    raise TypeJsonParseError('parser', 'only one value can appear', token.line_col)

    @staticmethod
    def hex_string_to_num(hex_num):
        return int(hex_num, 16)

    def parse_string(self):
        string = self.current.value
        line_col = self.current.line_col
        i = 0
        r = ''
        while True:
            if i >= len(string):
                return ast.String(r, line_col)
            c = string[i]
            if c == '\\':
                i += 1
                c = string[i]
                if c == 'u':
                    hex_num = string[i + 1:i + 5]
                    i += 4
                    c = string[i]
                    if c == '\\' and string[i + 1] == 'u':
                        i += 1
                        hex_num2 = string[i + 1:i + 5]
                        r += self.__encode_unicode(hex_num, hex_num2)
                        i += 4
                    else:
                        r += self.__encode_unicode(hex_num, None)
                elif c == 'b':
                    r += '\b'
                elif c == 'f':
                    r += '\f'
                elif c == 'n':
                    r += '\n'
                elif c == 'r':
                    r += '\r'
                elif c == 't':
                    r += '\t'
                else:
                    r += c
            else:
                r += c
            i += 1

    def __encode_unicode(self, esc, esc2):
        # Unicode escape sequence
        msg = "Invalid \\uXXXX escape sequence"
        try:
            uni = int(esc, 16)
        except ValueError:
            raise TypeJsonParseError('parser', msg, self.current.line_col)
        # Check for surrogate pair on UCS-4 systems
        # Note that this will join high/low surrogate pairs
        # but will also pass unpaired surrogates through
        if esc2 is not None and self._maxunicode > 65535 and uni & 0xfc00 == 0xd800:
            try:
                uni2 = int(esc2, 16)
            except ValueError:
                raise TypeJsonParseError('parser', msg, self.current.line_col)
            if uni2 & 0xfc00 == 0xdc00:
                uni = 0x10000 + (((uni - 0xd800) << 10) |
                                 (uni2 - 0xdc00))
        char = unichr(uni)
        # Append the unescaped character
        return char

    # expect token to be [ or (
    # the token will be ] or )
    # return an array instead of ast
    def parse_elements(self):
        arr = []
        is_first = True
        while True:
            if self.current.type == TokenType.END_ARRAY or self.current.type == TokenType.END_SET:
                return arr
            if is_first or self.current.type == TokenType.COMMA:
                self.__next_token()
            else:
                raise TypeJsonParseError('parser', 'expecting `:`', self.current.line_col)
            if self.current.type == TokenType.END_ARRAY or self.current.type == TokenType.END_SET:
                return arr
            is_first = False
            value = self.parse_value()
            arr.append(value)
            self.__next_token()

    # expect token to be (
    # the token will be )
    def parse_set(self):
        line_col = self.current.line_col
        elements = self.parse_elements()
        if self.current.type == TokenType.END_SET:
            return ast.Set(elements, line_col)
        else:
            raise TypeJsonParseError('parser', 'expecting )', self.current.line_col)

    # expect token to be [
    # the token will be ]
    def parse_array(self):
        line_col = self.current.line_col
        elements = self.parse_elements()
        if self.current.type == TokenType.END_ARRAY:
            return ast.Array(elements, line_col)
        else:
            raise TypeJsonParseError('parser', 'expecting ]', self.current.line_col)

    def calculate_e(self, base, exponent):
        if exponent == 0:
            if base == 0:
                raise TypeJsonParseError('parser', '0e0 is undefined', self.current.line_col)
            return 1
        e = exponent
        if e < 0:
            e = -e
        x = Parser.calculate_pow_10_n(e)

        if exponent < 0:
            return float(decimal.Decimal.from_float(base) / decimal.Decimal.from_float(x))
        else:
            return float(decimal.Decimal.from_float(base) * decimal.Decimal.from_float(x))

    @staticmethod
    def calculate_pow_10_n(n):
        if n == 0:
            return 1
        res = 1
        x = 10
        while n > 0:
            if n & 1 > 0:
                res *= x
            x *= x
            n >>= 1
        return res

    def parse_number(self):
        line_col = self.current.line_col
        num_string = self.current.value
        is_negative = False
        if num_string.startswith('-'):
            is_negative = True
            num_string = num_string[1:]
        pow_exponent = None
        if "e" in num_string or "E" in num_string:
            if "e" in num_string:
                split = num_string.split("e")
                pow_exponent = int(split[1])
                num_string = split[0]
            else:
                split = num_string.split("E")
                pow_exponent = int(split[1])
                num_string = split[0]
        is_float = False
        if "." in num_string or pow_exponent is not None:
            is_float = True
        if is_float:
            base = float(num_string)
            if pow_exponent:
                n = self.calculate_e(base, pow_exponent)
                if is_negative:
                    n = -n
                return ast.Number(n, True, line_col)
            else:
                if is_negative:
                    base = -base
                return ast.Number(base, True, line_col)
        else:
            base = int(num_string)
            if is_negative:
                base = -base
            return ast.Number(base, False, line_col)

    # expect token to be the value
    # the token will be the value
    def parse_value(self):
        line_col = self.current.line_col
        type_symbol = None
        if self.current.type == TokenType.VALID_NAME:
            type_symbol = self.parse_type_ref_symbol()

        value = None
        if self.current.type == TokenType.NULL:
            value = ast.Null(line_col)
        elif self.current.type == TokenType.STRING:
            value = self.parse_string()
        elif self.current.type == TokenType.BOOLEAN:
            boolean = self.current.value == "true"
            value = ast.Boolean(boolean, line_col)
        elif self.current.type == TokenType.NUMBER:
            value = self.parse_number()
        elif self.current.type == TokenType.START_OBJ:
            line_col = self.current.line_col
            value = self.parse_obj()
        elif self.current.type == TokenType.START_ARRAY:
            value = self.parse_array()
        elif self.current.type == TokenType.START_SET:
            value = self.parse_set()
        elif not value:
            raise TypeJsonParseError('parser', 'expecting a value', self.current.line_col)
        return ast.TypeValue(type_symbol, value, line_col)

    # expect token to be the key
    # the token will be after the value
    def parse_member(self):
        line_col = self.current.line_col
        if self.current.type == TokenType.VALID_NAME:
            key = ast.String(self.current.value, self.current.line_col)
        elif self.current.type == TokenType.STRING:
            key = self.parse_string()
        else:
            raise TypeJsonParseError('parser', 'expecting key of the member (string | valid name)',
                                     self.current.line_col)
        self.__next_token()
        if self.current.type != TokenType.COLON:
            raise TypeJsonParseError('parser', 'expecting :', self.current.line_col)
        self.__next_token()
        value = self.parse_value()
        self.__next_token()
        return ast.Member(key, value, line_col)

    # expect token to be `{`
    # the token will be `}`
    def parse_members(self):
        members = []
        is_first = True
        while True:
            if self.current.type == TokenType.END_OBJ:
                return members
            if is_first or self.current.type == TokenType.COMMA:
                self.__next_token()
            if self.current.type == TokenType.END_OBJ:
                return members
            is_first = False
            member = self.parse_member()
            members.append(member)

    # expect token to be type_name or `{`
    # the token will be `}`
    def parse_obj(self):
        line_col = self.current.line_col
        members = self.parse_members()
        return ast.Object(members, line_col)

    # expect token to be member
    # the token will be after the member
    def parse_def_member(self):
        line_col = self.current.line_col
        if self.current.type == TokenType.VALID_NAME:
            key_name = ast.String(self.current.value, self.current.line_col)
        elif self.current.type == TokenType.STRING:
            key_name = self.parse_string()
        else:
            raise TypeJsonParseError('parser', 'expecting key of the def member (valid name | string)',
                                     self.current.line_col)
        self.__next_token()
        if self.current.type != TokenType.COLON:
            raise TypeJsonParseError('parser', 'expecting :', self.current.line_col)
        self.__next_token()
        type_symbol = self.parse_type_symbol_optional()
        return ast.DefMember(key_name, type_symbol, line_col)

    # expect token to be '{'
    # the token will be '}'
    def parse_def_members(self):
        members = []
        is_first = True
        while True:
            if self.current.type == TokenType.END_OBJ:
                return members
            if is_first or self.current.type == TokenType.COMMA:
                self.__next_token()
            if self.current.type == TokenType.END_OBJ:
                return members
            is_first = False
            def_member = self.parse_def_member()
            members.append(def_member)

    # expect token to be `<`
    # the token will be after `>`
    def parse_generic_symbols(self):
        symbols = []
        is_first = True
        while True:
            if self.current.type == TokenType.END_GENERIC:
                self.__next_token()
                return symbols
            if is_first or self.current.type == TokenType.COMMA:
                self.__next_token()
            is_first = False

            line_col = self.current.line_col
            if self.current.type == TokenType.VALID_NAME:
                type_name = self.current.value
                self.__next_token()
                type_symbol = None
                if self.current.type == TokenType.COLON:
                    # T : U
                    self.__next_token()
                    type_symbol = self.parse_type_ref_symbol()
                symbols.append(ast.GenericTypeSymbol(type_name, type_symbol, line_col))
            else:
                raise TypeJsonParseError('parser', 'expecting type name (valid name)', self.current.line_col)

    # expect token to be `<`
    # the token will be after `>`
    def parse_generic_type_ref_symbols(self):
        symbols = []
        is_first = True
        while True:
            if self.current.type == TokenType.END_GENERIC:
                self.__next_token()
                return symbols
            if is_first or self.current.type == TokenType.COMMA:
                self.__next_token()
            is_first = False

            if self.current.type == TokenType.VALID_NAME:
                type_symbol = self.parse_type_ref_symbol()
                symbols.append(type_symbol)
            else:
                raise TypeJsonParseError('parser', 'expecting type name (valid name)', self.current.line_col)

    # expect token to be the type_symbol
    # the token will be after type_symbol or ?
    def parse_type_symbol_optional(self):
        line_col = self.current.line_col
        if self.current.type == TokenType.VALID_NAME:
            type_symbol = self.parse_type_ref_symbol()
            optional = False
            if self.current.type == TokenType.OPTIONAL:
                optional = True
                self.__next_token()
            return ast.OptionalTypeSymbol(type_symbol, optional, line_col)
        else:
            raise TypeJsonParseError('parser', 'expecting type name (valid name)', self.current.line_col)

    # expect token to be the type_symbol
    # the token will be after type_symbol
    def parse_type_symbol(self):
        line_col = self.current.line_col
        if self.current.type == TokenType.VALID_NAME:
            type_name = self.current.value
            self.__next_token()
            if self.current.type == TokenType.START_GENERIC:
                generic_type_symbols = self.parse_generic_symbols()
                type_symbol = ast.TypeSymbol(type_name, generic_type_symbols, line_col)
            else:
                type_symbol = ast.TypeSymbol(type_name, None, line_col)
            return type_symbol
        else:
            raise TypeJsonParseError('parser', 'expecting type name (valid name)', self.current.line_col)

    # expect token to be the type_ref_symbol
    # the token will be after type_ref_symbol
    def parse_type_ref_symbol(self):
        line_col = self.current.line_col
        if self.current.type == TokenType.VALID_NAME:
            type_name = self.current.value
            self.__next_token()
            if self.current.type == TokenType.START_GENERIC:
                type_ref_symbols = self.parse_generic_type_ref_symbols()
                type_ref_symbol = ast.TypeRefSymbol(type_name, type_ref_symbols, line_col)
            else:
                type_ref_symbol = ast.TypeRefSymbol(type_name, None, line_col)
            return type_ref_symbol
        else:
            raise TypeJsonParseError('parser', 'expecting type name (valid name)', self.current.line_col)

    # expect token to be `def`
    # the token will be `}`
    def parse_def(self):
        line_col = self.current.line_col
        self.__next_token()
        type_symbol = self.parse_type_symbol()
        parent_type_symbol = None
        if self.current.type == TokenType.COLON:
            self.__next_token()
            parent_type_symbol = self.parse_type_ref_symbol()
        if self.current.type == TokenType.START_OBJ:
            def_members = self.parse_def_members()
        else:
            raise TypeJsonParseError('parser', 'expecting definition object (object)', self.current.line_col)
        return ast.Def(type_symbol, parent_type_symbol, def_members, line_col)

    def parse_use(self):
        line_col = self.current.line_col
        self.__next_token()
        if self.current.type == TokenType.STRING:
            string = self.parse_string()
            return ast.Use(string, line_col)
        else:
            raise TypeJsonParseError('parser', 'expecting the module to use (string)', self.current.line_col)

    def parse_module(self):
        line_col = self.current.line_col
        self.__next_token()
        if self.current.type == TokenType.STRING:
            string = self.parse_string()
            return ast.Module(string, self.current.line_col)
        else:
            raise TypeJsonParseError('parser', 'expecting module name (string)', line_col)
